package ynote;

import com.xr.util.JSONUtil;

import javax.net.ssl.*;
import java.io.*;
import java.net.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;

/**
 * ynote上传
 * Created by forvoyager@outlook.com on 2018-10-15 15:08.
 */
public class Ynote {

  private static final String DEFAULT_CHARSET = "UTF-8";
  private static final String HTTP_METHOD_POST = "POST";
  private static final String HTTP_METHOD_GET = "GET";

  private static CookieManager cookieManager;

  private String targetDirCode = null;

  public Ynote() {
    cookieManager = new CookieManager();
    // cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
    CookieHandler.setDefault(cookieManager);
  }

  public static void main(String[] args) throws Exception {
//    String title = "笔记02";
//    String content = "<p>所以，第一个例子中的T(n) = O(2n+2)，第二个例子中的T(n) = O(2n<sup>2</sup>+2n+3)。这就是<strong>大O时间复杂度表示法</strong>。大O时间复杂度实际上并不具体表示代码真正的执行时间，而是表示<strong>代码执行时间随数据规模增长的变化趋势</strong>，所以，也叫作<strong>渐进时间复杂度</strong>（asymptotic time complexity），简称<strong>时间复杂度</strong>。</p>";

    Ynote ynote = new Ynote();
    ynote.login("username", "password");
    printCookie();
    ynote.loadDir("your target dir");

    StringBuffer content = new StringBuffer();
    BufferedReader br = new BufferedReader(new FileReader("E:\\content.txt"));
    String line = null;
    while ((line = br.readLine()) != null) {
      content.append(line.trim());
    }
    br.close();

    List<Article> articles = JSONUtil.parseList(content.toString(), Article.class);
    int articlesCount = articles.size();
    for(Article ar : articles){
      ynote.upload(
        articlesCount + "_" + ar.getArticle_title(),
        "<a href=\""+ar.getAudio_download_url()+"\">下载音频</a>"+ar.getArticle_content());
      Thread.sleep(5000);

      articlesCount--;
    }

    System.out.println("OK.");
  }

  private void login(String username, String password) throws Exception {
    Map<String, Object> params = new HashMap();
    params.put("user", username);
    params.put("pass", password);
    String result = post("http://note.youdao.com/yws/mapi/wcp?method=login&keyfrom=wcp", params);
    if (result.contains("错误")) {
      throw new RuntimeException("登陆失败，请检查用户名或者密码。");
    }

    loadCookie();
  }

  private void loadCookie() throws Exception {
    get("http://note.youdao.com/yws/mapi/user?keyfrom=web&method=get&_=" + new Date().getTime(), null);
  }

  private void loadDir(String targetDir) throws Exception {
    String result = get("http://note.youdao.com/yws/api/personal/share/subdirs?cstk=" + getCookie("YNOTE_CSTK"), null);
    if (!result.startsWith("{")) {
      throw new RuntimeException("获取目录结构失败");
    }

    System.out.println("获取笔记目录:" + result);
    result = result.substring(1, result.length() - 1);
    for (String itm : result.split("\"},")) {
      if (targetDir.equals(itm.substring(itm.indexOf("name") + 7))) {
        this.targetDirCode = itm.substring(1, itm.indexOf(":") - 1);
        break;
      }
    }
  }

  private void upload(String title, String content) throws Exception {
    if (this.targetDirCode == null) {
      throw new RuntimeException("上传目录不正确");
    }

    Map<String, Object> params = new HashMap();
    params.put("bs", encode(content));
    params.put("p", "/" + new Date().getTime());
    params.put("tl", title.replaceAll(" \\| ","_").replaceAll("\\?", ""));
    params.put("src", "");
    params.put("e", "false");
    params.put("len", content.length());
    params.put("type", "MainBody");
    params.put("nb", this.targetDirCode);
    params.put("from", "");
    params.put("confirm", "true");
    params.put("ml", "true");
    params.put("ourl", "false");
    String result = post("http://note.youdao.com/yws/mapi/wcp?method=update&keyfrom=wcp", params);
    if (result.contains("保存成功")) {
      System.out.println("上传笔记成功");
    } else {
      System.err.println("上传笔记失败，title："+title);
    }

  }


  private String get(String url, Map<String, Object> params) throws Exception {
    StringBuffer finalUrl = new StringBuffer();
    if (params != null && params.size() > 0) {
      for (String key : params.keySet()) {
        finalUrl.append("&").append(key).append("=").append(URLEncoder.encode(params.get(key).toString(), DEFAULT_CHARSET));
      }
      finalUrl.replace(0, 1, "?");
    }
    finalUrl.insert(0, url);

    String boundary = "---------------------------" + new Date().getTime();
    HttpURLConnection conn = getConnection(finalUrl.toString(), HTTP_METHOD_GET, boundary);

    String result = readStream(conn.getInputStream(), conn.getContentEncoding());
    conn.disconnect();

    return result;
  }

  private String post(String url, Map<String, Object> params) throws Exception {
    String boundary = "---------------------------" + new Date().getTime();
    HttpURLConnection conn = getConnection(url, HTTP_METHOD_POST, boundary);

    if (params != null && params.size() > 0) {
      /**
       * ynote网页简报
       * Content-Type: multipart/form-data; boundary=${bound}
       * 所以参数需要特殊处理（不是key1=value&key2=）
       */
      StringBuffer payload = new StringBuffer();
      for (String key : params.keySet()) {
        payload = payload.append("--").append(boundary);
        payload.append("\r\n");
        payload.append("Content-Disposition: form-data; name=\"" + key + "\"");
        payload.append("\r\n\r\n"); // 提交的数据前要有两个回车换行
        payload.append(params.get(key).toString());
        payload.append("\r\n");
      }
      payload.append("--").append(boundary).append("--").append("\r\n"); // 结束标记 boundary前后各需添加两上横线，最添加添回车换行

      OutputStream outwritestream = conn.getOutputStream();
      outwritestream.write(payload.toString().getBytes(DEFAULT_CHARSET));
      outwritestream.flush();
      outwritestream.close();
    }

    StringBuffer response = new StringBuffer();

    int responsecode = conn.getResponseCode();
    if (responsecode == 200) {
      response.append(readStream(conn.getInputStream(), conn.getContentEncoding()));
    } else {
      System.out.println("http status code:" + responsecode);
    }

    return response.toString();
  }

  private HttpURLConnection getConnection(String url, String type, String boundary) throws Exception {

    ignoreSsl();

    HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
    HttpURLConnection.setFollowRedirects(true);
    conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0");
    conn.setRequestProperty("Host", "note.youdao.com");
    conn.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
    conn.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
    conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
//    conn.setRequestProperty("Referer", "http://note.youdao.com/yws/mapi/wcp?method=login&keyfrom=wcp");
    conn.setRequestProperty("Connection", "keep-alive");
    conn.setRequestProperty("Upgrade-Insecure-Requests", "1");
    conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
    conn.setRequestMethod(type);
    if (HTTP_METHOD_POST.equals(type)) {
      conn.setDoOutput(true);
    }
    conn.setDoInput(true);
    conn.setUseCaches(false);
    conn.setInstanceFollowRedirects(true);
    return conn;
  }

  private static String readStream(InputStream inputStream, String encoding) throws Exception {

    InputStreamReader inputStreamReader = null;
    GZIPInputStream gZIPInputStream = null;
    if ("gzip".equals(encoding)) {
      gZIPInputStream = new GZIPInputStream(inputStream);
      inputStreamReader = new InputStreamReader(gZIPInputStream, DEFAULT_CHARSET);
    } else {
      inputStreamReader = new InputStreamReader(inputStream, DEFAULT_CHARSET);
    }

    int len;
    char[] buf = new char[8 * 1024];
    StringBuffer content = new StringBuffer();
    while ((len = inputStreamReader.read(buf)) != -1) {
      content.append(new String(buf, 0, len));
    }
    if (inputStream != null) {
      inputStream.close();
    }
    if (gZIPInputStream != null) {
      gZIPInputStream.close();
    }

    return content.toString();
  }

  private void ignoreSsl() throws Exception {
    TrustManager tm = new IgnoreAllTM();
    SSLContext sc = SSLContext.getInstance("SSL");
    sc.init(null, new TrustManager[]{tm}, null);
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

    HostnameVerifier hv = new HostnameVerifier() {
      public boolean verify(String urlHostName, SSLSession session) {
        return true;
      }
    };
    HttpsURLConnection.setDefaultHostnameVerifier(hv);
  }

  public static void printCookie() {
    List<HttpCookie> listCookie = cookieManager.getCookieStore().getCookies();
    listCookie.forEach(httpCookie -> {
      System.out.println("--------------------------------------");
      System.out.println("class      : " + httpCookie.getClass());
      System.out.println("comment    : " + httpCookie.getComment());
      System.out.println("commentURL : " + httpCookie.getCommentURL());
      System.out.println("discard    : " + httpCookie.getDiscard());
      System.out.println("domain     : " + httpCookie.getDomain());
      System.out.println("maxAge     : " + httpCookie.getMaxAge());
      System.out.println("name       : " + httpCookie.getName());
      System.out.println("path       : " + httpCookie.getPath());
      System.out.println("portlist   : " + httpCookie.getPortlist());
      System.out.println("secure     : " + httpCookie.getSecure());
      System.out.println("value      : " + httpCookie.getValue());
      System.out.println("version    : " + httpCookie.getVersion());
      System.out.println("httpCookie : " + httpCookie);
    });
  }

  private static String getCookie(String key) {
    List<HttpCookie> listCookie = cookieManager.getCookieStore().getCookies();
    for (HttpCookie hc : listCookie) {
      if (key.equals(hc.getName())) {
        return hc.getValue();
      }
    }

    return "";
  }

  /**
   * 将中文内容转换为html实体
   *
   * @param str
   * @return
   */
  private String encode(String str) {
    char[] arrs = str.toCharArray();
    StringBuilder sb = new StringBuilder();
    for (char c : arrs) {
      if (c >= '\u2E80' && c <= '\uFE4F') {
        sb.append("&#").append((int) c).append(";");
      } else {
        sb.append(c);
      }
    }
    return sb.toString();
  }

  class IgnoreAllTM implements TrustManager, X509TrustManager {
    public X509Certificate[] getAcceptedIssuers() {
      return null;
    }

    public void checkServerTrusted(X509Certificate[] certs, String authType)
      throws CertificateException {
      return;
    }

    public void checkClientTrusted(X509Certificate[] certs, String authType)
      throws CertificateException {
      return;
    }
  }
}
class Article {
  private String article_content;
  private String article_title;
  private String audio_download_url;

  public Article(){}

  public String getArticle_content() {
    return article_content;
  }

  public void setArticle_content(String article_content) {
    this.article_content = article_content;
  }

  public String getArticle_title() {
    return article_title;
  }

  public void setArticle_title(String article_title) {
    this.article_title = article_title;
  }

  public String getAudio_download_url() {
    return audio_download_url;
  }

  public void setAudio_download_url(String audio_download_url) {
    this.audio_download_url = audio_download_url;
  }
}
